package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.common.auth.pojo.AppInfo;
import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * User: ycs
 * Date: 2020/8/30
 * Time: 16:31
 * Description: No Description
 *
 * @author ycs
 */
@Slf4j
@Service
public class AuthService {
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private UserClient userClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired(required = false)
    private ApplicationInfoMapper appMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public void logIn(String username, String password, HttpServletRequest request, HttpServletResponse response) {
        User user = userClient.findByUsernameAndPassword(username, password);
        //封装载荷中的数据
        UserInfo userInfo = new UserInfo();

        userInfo.setId(user.getId());
        userInfo.setRole("admin");
        userInfo.setUsername(user.getUsername());
        //创建token做成cookie放入浏览器
        GenerateTokenWriteToCookie(request, response, userInfo);
    }

    /**
     * 创建token做成cookie放入浏览器方法
     **/
    private void GenerateTokenWriteToCookie(HttpServletRequest request, HttpServletResponse response, UserInfo userInfo) {
        //生成token
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getCookie().getExpire());
        //创建cookie并放入浏览器中
        CookieUtils.newCookieBuilder()
                .request(request)
                .response(response)
                .domain(jwtProperties.getCookie().getCookieDomain())
                .name(jwtProperties.getCookie().getCookieName())
                .value(token).build();
    }

    /**
     * 页面显示登录信息 登录验证
     *
     * @param request
     * @param response
     * @return
     */
    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookie().getCookieName());
        //没有token证明没登录
        if (StringUtils.isEmpty(token)) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        Payload<UserInfo> payload;
        try {
            //解析token
            payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        UserInfo userInfo = payload.getUserInfo();
        //每次刷新页面 刷新token,过期时间减去15分钟
        DateTime dateTime = new DateTime(payload.getExpiration()).minusMinutes(jwtProperties.getCookie().getRefreshTime());
        //如果过了刷新时间点
        if (dateTime.isBeforeNow()) {
            //刷新从新生成token
            GenerateTokenWriteToCookie(request, response, userInfo);
        }
        //从radis(黑名单)获取token
        if (redisTemplate.hasKey(payload.getId())) {
            //token失效
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        return userInfo;
    }

    /**
     * 退出登录
     *
     * @param request
     * @param response
     */
    public void logout(HttpServletRequest request, HttpServletResponse response) {
        //获取token
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookie().getCookieName());
        if (StringUtils.isEmpty(token)) {
            return;
        }
        Payload<UserInfo> payload;
        try {
            //解析token
            payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
            long remaTime = payload.getExpiration().getTime() - System.currentTimeMillis();
            //用户信息还有大于6秒才过期那就设置黑名单,设置时间和他过期时间一致
            if (remaTime > 6000) {
                redisTemplate.opsForValue().set(payload.getId(), "", remaTime, TimeUnit.MILLISECONDS);
            }
        } catch (Exception e) {
            throw new LyException("当前用户信息已失效", 501);
        }
        //删除Cookie
        CookieUtils.deleteCookie(jwtProperties.getCookie().getCookieName(), jwtProperties.getCookie().getCookieDomain(), response);
    }

    /**
     * 给服务签发token
     *
     * @param id
     * @param secret
     * @return
     */
    public String authorization(Long id, String secret) {
        //判断服务账号是否有效
        if (!isUsable(id,secret)){
            //无效
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }
        //获取服务可以访问其他服务的集合
       List<Long>targetList= appMapper.queryTargetIdList(id);
        //封装载荷对象
        AppInfo appInfo = new AppInfo(id, secret, targetList);
        //生成token
        String token =
                JwtUtils
                        .generateTokenExpireInMinutes(appInfo, jwtProperties.getPrivateKey(), jwtProperties.getApp().getExpire());
        return token;
    }

    /**
     * 判断微服务账号密码是否正确
     *
     * @return
     */
    public Boolean isUsable(Long id, String secret) {
        //获取账号
        ApplicationInfo applicationInfo = appMapper.selectById(id);
        if (applicationInfo == null) {
            log.error("无该服务信息!!!!");
            return false;
        }
        //判断密码
        boolean matches = passwordEncoder.matches(secret, applicationInfo.getSecret());
        if (!matches) {
            log.error("服务密码错误!!!!");
            return false;
        }
        return true;
    }
}
